<!--
 * @Author: Li Zengkun
 * @Date: 2022-09-05 19:37:29
 * @LastEditors: Li Zengkun
 * @LastEditTime: 2022-09-07 15:23:26
 * @Description: 
 * @Description: 
-->
<template>
  <div>
    <a-breadcrumb>
      <a-breadcrumb-item>底层原理</a-breadcrumb-item>
      <a-breadcrumb-item>Observe Data</a-breadcrumb-item>
      <a-breadcrumb-item>Vue 3</a-breadcrumb-item>
    </a-breadcrumb>
    <div class="article-content">
      <h1 style="text-align: start; line-height: 1.5">
        😁😁vue3.0 响应式原理（基于ES6的Proxy、Reflect）
      </h1>
      <UserCard datetime="2022-09-05 19:37:29" />
      <div id="preface" v-html="code1"></div>
      <pre><code class='language-javascript line-numbers'>{{codejs1}}</code></pre>
      <h2>😘Vue3.0 响应式原理示意图</h2>
      <a-image
      class="img"
      width="80%"
        :src="vue3img"
      />
      <div id="proxy" v-html="code2"></div>
      <pre><code class='language-javascript line-numbers'>{{codejs2}}</code></pre>
      <div v-html="code3"></div>
      <pre><code class='language-javascript line-numbers'>{{codejs3}}</code></pre>
      <div v-html="code5"></div>
      <a-divider></a-divider>
      <div v-html="code4"></div>
      <pre><code class='language-javascript line-numbers'>{{codejs4}}</code></pre>
      <a-divider></a-divider>
      <h2>😘Proxy实现数据劫持(观察者模式)</h2>
      <p>
        观察者模式（Observer
        mode）指的是函数自动观察数据对象，一旦对象有变化，函数就会自动执行。
      </p>
      <pre><code class='language-javascript line-numbers'>{{codejs5}}</code></pre>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from "vue";
import UserCard from "@/components/UserCard.vue";
import Prism from "prismjs";
import vue3img from '@/assets/imgs/Vue3reactive.png'

onMounted(() => {
  Prism.highlightAll();
});
// const code1 = ref(`<h2 style="text-align: start; line-height: 1.5;">😁前言：</h2><p><span style="color: rgb(33, 37, 41); background-color: rgb(255, 255, 255); font-size: 16px;">Vue3的数据响应与劫持是基于现代浏览器所支持的代理对象Proxy实现的。</span><span style="color: rgb(64, 64, 64); background-color: rgb(255, 255, 255); font-size: 16px;">取代之前使用的 Object.defineProperty。</span></p><p><span style="color: rgb(64, 64, 64); background-color: rgb(255, 255, 255); font-size: 16px;">vue2的 Object.defineProperty 方法存在一定的局限性</span></p><ul><li><span style="color: rgb(64, 64, 64); font-size: 16px;">在Vue中，Object.defineProperty 无法监控到数组下标的变化，导致直接通过数组的下标给数组设置值，不能实时响应。 为了解决这个问题，经过vue内部处理后可以使用以下几种方法来监听数组 </span><span style="color: rgb(64, 64, 64); background-color: rgb(251, 233, 230); font-size: 16px;">push、pop、shift、unshift、splice、sort、reverse</span></li></ul><ul><li>Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里，是通过递归以及遍历data对象来实现对数据的监控的，如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象，不管是对操作性还是性能都会有一个很大的提升。</li></ul><hr/><h2>😝Proxy：</h2><h3>概述：</h3><p><span style="color: rgb(13, 20, 30);">Proxy 用于修改某些操作的默认行为，等同于在语言层面做出修改，所以属于一种“</span><span style="color: rgb(13, 20, 30); background-color: rgb(251, 233, 230);">元编程”（meta programming）</span><span style="color: rgb(13, 20, 30);">，即对编程语言进行编程。</span></p><p><span style="color: rgb(13, 20, 30);">Proxy 可以理解成，在目标对象之前架设一层“拦截”，外界对该对象的访问，都必须先通过这层拦截，因此提供了一种机制，可以对外界的访问进行过滤和改写。Proxy 的意思是代理，用在这里表示用它来“代理”某些操作，可以理解为“代理器”。</span></p><pre style="text-align: left;"><code class="language-javascript" style="text-align: left;">var obj = new Proxy({}, { get: function (target, propKey, receiver) { console.log(\`getting \${propKey}!\`); return Reflect.get(target, propKey, receiver); }, set: function (target, propKey, value, receiver) { console.log(\`setting \${propKey}!\`); return Reflect.set(target, propKey, value, receiver); } });</code></pre><p><br></p><p><br></p>`)
const code1 = ref(
  `<h2 style="text-align: start; line-height: 1.5;">😁前言：</h2><p><span style="color: rgb(33, 37, 41); background-color: rgb(255, 255, 255); font-size: 16px;">Vue3的数据响应与劫持是基于现代浏览器所支持的代理对象Proxy实现的。</span><span style="color: rgb(64, 64, 64); background-color: rgb(255, 255, 255); font-size: 16px;">取代之前使用的 Object.defineProperty。</span></p><p><span style="color: rgb(64, 64, 64); background-color: rgb(255, 255, 255); font-size: 16px;">vue2的 Object.defineProperty 方法存在一定的局限性</span></p><ul><li><span style="color: rgb(64, 64, 64); font-size: 16px;">在Vue中，Object.defineProperty 无法监控到数组下标的变化，导致直接通过数组的下标给数组设置值，不能实时响应。 为了解决这个问题，经过vue内部处理后可以使用以下几种方法来监听数组 </span><span style="color: rgb(64, 64, 64); background-color: rgb(251, 233, 230); font-size: 16px;">push、pop、shift、unshift、splice、sort、reverse</span></li></ul><ul><li>Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里，是通过递归以及遍历data对象来实现对数据的监控的，如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象，不管是对操作性还是性能都会有一个很大的提升。</li><li>不支持 Map、 Set、 WeakMap 和 WeakSet</li></ul><hr/><h2>😝Proxy：</h2><h3>概述：</h3><p><span style="color: rgb(13, 20, 30);">Proxy 用于修改某些操作的默认行为，等同于在语言层面做出修改，所以属于一种“</span><span style="color: rgb(13, 20, 30); background-color: rgb(251, 233, 230);">元编程”（meta programming）</span><span style="color: rgb(13, 20, 30);">，即对编程语言进行编程。</span></p><p><span style="color: rgb(13, 20, 30);">Proxy 可以理解成，在目标对象之前架设一层“拦截”，外界对该对象的访问，都必须先通过这层拦截，因此提供了一种机制，可以对外界的访问进行过滤和改写。Proxy 的意思是代理，用在这里表示用它来“代理”某些操作，可以理解为“代理器”。</span></p>`
);
const codejs1 = ref(`var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(\`getting \${propKey}!\`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(\`setting \${propKey}!\`);
    return Reflect.set(target, propKey, value, receiver);
  }
});`);
const code2 = ref(
  `<p style="text-align: start;">上面代码对一个空对象架设了一层拦截，重定义了属性的读取（<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>get</code></span>）和设置（<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>set</code></span>）行为。这里暂时先不解释具体的语法，只看运行结果。对设置了拦截行为的对象<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>obj</code></span>，去读写它的属性，就会得到下面的结果。</p>`
);
const codejs2 = ref(`obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2`);
const code3 = ref(
  `<p style="text-align: start;">ES6 原生提供 Proxy 构造函数，用来生成 Proxy 实例。</p><pre style="text-align: left;"><code class="language-javascript" style="text-align: left;">var proxy = new Proxy(target, handler);</code></pre><p style="text-align: start;">Proxy 对象的所有用法，都是上面这种形式，不同的只是<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>handler</code></span>参数的写法。其中，<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>new Proxy()</code></span>表示生成一个<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>Proxy</code></span>实例，<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>target</code></span>参数表示所要拦截的目标对象，<span style="color: rgb(199, 37, 78); background-color: rgb(249, 242, 244);"><code>handler</code></span>参数也是一个对象，用来定制拦截行为。</p><p style="text-align: start;">下面是另一个拦截读取属性行为的例子。</p>`
);
const codejs3 = ref(`var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35`);

const code4 = ref(
  `<h2 style="text-align: start;">😊Reflect</h2><p><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"><strong>Reflect</strong></span><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;"> 是一个内置的对象，它提供拦截 JavaScript 操作的方法。这些方法与</span><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy" target="" style="text-align: start;">proxy handlers (en-US)</a><span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">的方法相同。</span>Reflect<span style="color: rgb(27, 27, 27); background-color: rgb(255, 255, 255); font-size: 16px;">不是一个函数对象，因此它是不可构造的。</span></p><h3 style="text-align: start;">描述：</h3><p style="text-align: start;">与大多数全局对象不同<span style="background-color: var(--code-background-inline);"><code>Reflect</code></span>并非一个构造函数，所以不能通过<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new" target="">new 运算符</a>对其进行调用，或者将<span style="background-color: var(--code-background-inline);"><code>Reflect</code></span>对象作为一个函数来调用。<span style="background-color: var(--code-background-inline);"><code>Reflect</code></span>的所有属性和方法都是静态的（就像<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math" target=""><span style="background-color: var(--code-background-inline);"><code>Math</code></span></a>对象）。</p><p style="text-align: start;"><span style="background-color: var(--code-background-inline);"><code>Reflect</code></span> 对象提供了以下静态方法，这些方法与<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy" target="">proxy handler methods (en-US)</a>的命名相同。</p><p style="text-align: start;">其中的一些方法与 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object" target=""><span style="background-color: var(--code-background-inline);"><code>Object</code></span></a> 相同，尽管二者之间存在 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods" target="">某些细微上的差别</a>。</p><h3 style="text-align: start;">实例：</h3>`
);
const codejs4 = ref(`// 1-检测一个对象是否存在特定属性
const duck = {
  name: 'Maurice',
  color: 'white',
  greeting: function() {
    console.log(\`Quaaaack! My name is \${this.name}\`);
  }
}

Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false


// 2-返回这个对象的自身的属性
Reflect.ownKeys(duck);
// [ "name", "color", "greeting" ]


// 3-为这个对象添加一个新的属性
Reflect.set(duck, 'eyes', 'black');
// returns "true" if successful
// "duck" now contains the property "eyes: 'black'"`);

const code5 = ref(
  `<p>Proxy 一共 支持13 种的拦截,相对Object.defineProperty更加丰富。</p><ul><li><strong>get</strong>(target, propKey, receiver)：拦截对象属性的读取，比如proxy.foo和proxy['foo']。</li><li><strong>set</strong>(target, propKey, value, receiver)：拦截对象属性的设置，比如proxy.foo = v或proxy['foo'] = v，返回一个布尔值。</li><li><strong>has</strong>(target, propKey)：拦截propKey in proxy的操作，返回一个布尔值。</li><li>deleteProperty(target, propKey)：拦截delete proxy[propKey]的操作，返回一个布尔值。</li><li><strong>ownKeys</strong>(target)：拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环，返回一个数组。该方法返回目标对象所有自身的属性的属性名，而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。</li><li><strong>getOwnPropertyDescriptor</strong>(target, propKey)：拦截Object.getOwnPropertyDescriptor(proxy, propKey)，返回属性的描述对象。</li><li><strong>defineProperty</strong>(target, propKey, propDesc)：拦截Object.defineProperty(proxy, propKey, propDesc）、Object.defineProperties(proxy, propDescs)，返回一个布尔值。</li><li><strong>preventExtensions</strong>(target)：拦截Object.preventExtensions(proxy)，返回一个布尔值。</li><li><strong>getPrototypeOf</strong>(target)：拦截Object.getPrototypeOf(proxy)，返回一个对象。</li><li><strong>isExtensible</strong>(target)：拦截Object.isExtensible(proxy)，返回一个布尔值。</li><li><strong>setPrototypeOf</strong>(target, proto)：拦截Object.setPrototypeOf(proxy, proto)，返回一个布尔值。如果目标对象是函数，那么还有两种额外操作可以拦截。</li><li><strong>apply</strong>(target, object, args)：拦截 Proxy 实例作为函数调用的操作，比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。</li><li><strong>construct</strong>(target, args)：拦截 Proxy 实例作为构造函数调用的操作，比如new proxy(...args)。</li></ul>`
);
const codejs5 = ref(`observe(data) {
  const that = this;
  let handler = {
   get(target, key) {
      return target[key];
    },
    set(target, key, value) {
      let res = Reflect.set(target, key, value);
      that.subscribe[key].map(item => {
        item.update();
      });
      return res;
    }
  }
  // $data:从 data 选项函数中返回的对象，会被组件赋为响应式。
  //       组件实例将会代理对其数据对象的属性访问。
  this.$data = new Proxy(data, handler);
}`);
</script>

<style lang="less" scoped>


.img{
  text-align: center;
  width: 80% !important;
  // max-width:800px;
}
</style>
